package com.ehotting.edsta.circuitDesign.gen;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.lang.SerializationUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

public  class CircuitAllocationTest2 {

    private final static Integer EXPONENT_LEVEL = 4;

    private final static Integer QUERY_SIZE = 3;

    public static void main(String[] args) {

        List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList  =  getInputChannel();
        List<CircuitAllocationTest2.CircuitTestBean> allCircuitTestList = getCircuitTestList();

        List<CircuitAllocationTest2.CircuitGroupTestBean> circuitGroupTestList  = handler(
                inputSamplingList,
                 allCircuitTestList);
        System.out.println(JSON.toJSONString(circuitGroupTestList, SerializerFeature.DisableCircularReferenceDetect));
//        int i =(int) Math.round(Math.log(13) / Math.log(2));
//
//        System.out.println("i:"+i);

    }


    public static List<CircuitAllocationTest2.CircuitGroupTestBean> handler(
            List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList,
            List<CircuitAllocationTest2.CircuitTestBean> allCircuitTestList) {
        List<CircuitAllocationTest2.CircuitGroupTestBean> circuitGroupTestList = new ArrayList<>();

        for(int i=EXPONENT_LEVEL ; i>=0 ; i--) {
            System.out.println("___i:"+i);
            List<CircuitAllocationTest2.CircuitGroupTestBean> cgtList = recursionHandler(inputSamplingList,
                    allCircuitTestList,
                    i, 0);
            cgtList = filter(cgtList);
            circuitGroupTestList.addAll(cgtList);
            circuitGroupTestList  = filter(circuitGroupTestList);
            if(circuitGroupTestList.size()>=QUERY_SIZE){
                break;
            }
        }

        circuitGroupTestList = circuitGroupTestList.stream()
                .sorted((a,b)->a.getAllChannelCount()-b.getAllChannelCount())
                .sorted((a,b)->a.getCircuitCount()-b.getCircuitCount())
                .limit(3)
                .collect(Collectors.toList());

       return circuitGroupTestList;
    }

    private static  List<CircuitAllocationTest2.CircuitGroupTestBean> filter(List<CircuitAllocationTest2.CircuitGroupTestBean> circuitGroupTestList){
        Map<Set<Integer>,Object> exist = new HashMap<>();
        for(CircuitAllocationTest2.CircuitGroupTestBean cgt : circuitGroupTestList){
            exist.put(cgt.keySet(),cgt);
        }
        return new ArrayList(exist.values());
    }




    private static List<CircuitAllocationTest2.CircuitGroupTestBean> recursionHandler(List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList ,
                                                                                      List<CircuitAllocationTest2.CircuitTestBean> allCircuitTestList,
                                                                                      Integer channelExponent,
                                                                                      Integer exponentLevel){

        List<CircuitAllocationTest2.CircuitGroupTestBean> cgtList = new ArrayList<>();
        if(channelExponent<0){
            return  cgtList;
        }
        Integer channelNum = (int) Math.pow(2, channelExponent);
        List<List<CircuitAllocationTest2.ChannelTestBean>>  groupList = groupChannel(inputSamplingList,channelNum);

        channelExponent--;

        List<List<CircuitAllocationTest2.CircuitGroupTestBean>> cgList = new ArrayList<>();
        for(int i=0;i<groupList.size();i++){
            List<CircuitAllocationTest2.ChannelTestBean> ctList = groupList.get(i);
            if(ctList.size()==0){
                continue;
            }
            List<CircuitAllocationTest2.CircuitGroupTestBean> circuitGroupTestList = handleCircuitGroup(ctList,allCircuitTestList,exponentLevel);
            if(circuitGroupTestList.size()==0){
                circuitGroupTestList = recursionHandler(ctList,allCircuitTestList,channelExponent,exponentLevel);
            }
            if(circuitGroupTestList.size()==0){
                return  cgtList;
            }

            cgList.add(circuitGroupTestList);
        }
        cgtList = cross(cgList);

        return cgtList;
    }

    private static List<CircuitAllocationTest2.CircuitGroupTestBean> cross(List<List<CircuitAllocationTest2.CircuitGroupTestBean>> cgList){
        List<CircuitAllocationTest2.CircuitGroupTestBean> newCgTestList = new ArrayList<>();
        CircuitAllocationTest2.CircuitGroupTestBean circuitGroupTest = new CircuitAllocationTest2.CircuitGroupTestBean();
        newCgTestList.add(circuitGroupTest);
        for(List<CircuitAllocationTest2.CircuitGroupTestBean> cgTestList : cgList){
            newCgTestList = cross1(newCgTestList, cgTestList);
        }
        return newCgTestList;
    }

    private static List<CircuitAllocationTest2.CircuitGroupTestBean> cross1(
            List<CircuitAllocationTest2.CircuitGroupTestBean> cgTestList1,
            List<CircuitAllocationTest2.CircuitGroupTestBean> cgTestList2){
        List<CircuitAllocationTest2.CircuitGroupTestBean> newCgTestList = new ArrayList<>();
        for(CircuitAllocationTest2.CircuitGroupTestBean cgt1 : cgTestList1){
            for(CircuitAllocationTest2.CircuitGroupTestBean cgt2 : cgTestList2){
                CircuitAllocationTest2.CircuitGroupTestBean newCircuitGroupTest = new CircuitAllocationTest2.CircuitGroupTestBean();
                newCircuitGroupTest.putAll(cgt1.clone());
                newCircuitGroupTest.putAll(cgt2.clone());
                newCgTestList.add(newCircuitGroupTest);
            }
        }
        return newCgTestList;
    }


    private static List<CircuitAllocationTest2.CircuitGroupTestBean> handleCircuitGroup(List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList ,
                                                                                       List<CircuitAllocationTest2.CircuitTestBean> allCircuitTestList,
                                                                                        Integer exponentLevel){

        List<CircuitAllocationTest2.CircuitGroupTestBean> circuitGroupTestList = new ArrayList<>();
        if(inputSamplingList.size()==0){
            return circuitGroupTestList;
        }

        Integer inputChannelSize = inputSamplingList.size();

        List<CircuitAllocationTest2.CircuitTestBean> circuitTestList = findCircuitTestList( inputSamplingList , allCircuitTestList,0,new ChannelEquals() {
            @Override
            public boolean invoke(CircuitTestBean t,Integer exponentIndex) {
                Integer exponent = (int)(Math.round(Math.log(inputChannelSize) / Math.log(2)))+exponentIndex;
                return t.getChannelNum().equals(inputChannelSize);
            }
        });

        if(circuitTestList.size()<QUERY_SIZE){
            List<CircuitAllocationTest2.CircuitTestBean> ctList = findCircuitTestList( inputSamplingList , allCircuitTestList,0,new ChannelEquals() {
                @Override
                public boolean invoke(CircuitTestBean t,Integer exponentIndex) {
                    Integer exponent = (int)(Math.log(inputChannelSize) / Math.log(2));
                    double median= 1.0 * ( Math.pow(2,exponent)+ Math.pow(2,exponent+1))/2;
                    boolean flag = false;
                    if(inputChannelSize<= median){
                        flag =  inputChannelSize<=t.getChannelNum() && t.getChannelNum()<=median;
                    }
                    if((!flag) && inputChannelSize >=median ){
                        return inputChannelSize<=t.getChannelNum() && t.getChannelNum()<= Math.pow(2,exponent+1);
                    }
                    return flag;
                }
            });
            if(ctList.size()>0){
                circuitTestList.addAll(ctList);
            }
        }




        if(circuitTestList.size()<QUERY_SIZE && exponentLevel>0){
            for(Integer exponentIndex=0;exponentIndex<exponentLevel;exponentIndex++){
                List<CircuitAllocationTest2.CircuitTestBean> ctList = findCircuitTestList(inputSamplingList, allCircuitTestList, exponentIndex,new ChannelEquals() {
                    @Override
                    public boolean invoke(CircuitTestBean t,Integer exponentIndex) {
                        Integer exponent = (int)(Math.ceil(Math.log(inputChannelSize) / Math.log(2)))+exponentIndex;
                        if(exponent<=4){
                            return t.getChannelNum() >= inputChannelSize && t.getChannelNum()<= Math.pow(2,exponent) ;
                        }
                        return false;
                    }
                });
                if(ctList.size()>0){
                    circuitTestList.addAll(ctList);
                }

                if(circuitTestList.size()>=5){
                    break;
                }
            }
        }

        circuitGroupTestList = circuitTestList.stream().map(t->{
            CircuitAllocationTest2.CircuitGroupTestBean cGroup = new CircuitAllocationTest2.CircuitGroupTestBean();
            CircuitAllocationTest2.CircuitTestBean newCircuitTest = t.clone();
            newCircuitTest.setCount((int)Math.ceil(1.0 * inputChannelSize / t.getChannelNum()));
            newCircuitTest.setChannelTestList(new ArrayList<>(inputSamplingList));
            cGroup.put(t.getId(),newCircuitTest);
            return cGroup;
        }).collect(Collectors.toList());

        return circuitGroupTestList;
    }


    private static List<CircuitAllocationTest2.CircuitTestBean> findCircuitTestList(
            List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList,
            List<CircuitAllocationTest2.CircuitTestBean> allCircuitTestList,
            Integer exponentIndex,
            ChannelEquals ce){
        CircuitAllocationTest2.ChannelTestBean inputMaxSampling = inputSamplingList.stream().max((o1, o2) -> o1.getSampling() - o2.getSampling()).get();
        List<CircuitAllocationTest2.CircuitTestBean> circuitTestList = allCircuitTestList.stream().filter(t->{
            boolean isCircuitEqual =  isCircuitEquals(t, inputMaxSampling) ;
            boolean isChannelEqual = ce.invoke(t,exponentIndex);
            return isCircuitEqual  && isChannelEqual;
        }).collect(Collectors.toList());
        return circuitTestList;
    }

    public interface ChannelEquals {
        boolean invoke(CircuitAllocationTest2.CircuitTestBean t, Integer exponentIndex);
    }

    private static boolean isCircuitEquals(CircuitAllocationTest2.CircuitTestBean t, CircuitAllocationTest2.ChannelTestBean inputMaxSampling){
        return t.getSampling() >= inputMaxSampling.getSampling();
    }

    private static List<List<CircuitAllocationTest2.ChannelTestBean>> groupChannel(
            List<CircuitAllocationTest2.ChannelTestBean> inputSamplingList,
            Integer channelNum){
        int groupNum =(int) Math.ceil(1.0 * inputSamplingList.size() / channelNum);
        List<List<CircuitAllocationTest2.ChannelTestBean>> groupList = new ArrayList();
        for(int j=0;j<groupNum;j++){
            List<CircuitAllocationTest2.ChannelTestBean> list = new ArrayList<>();
            for(int i=0;i< inputSamplingList.size();i++){
                if( channelNum * j <=i &&  i < channelNum * (j+1) ){
                    CircuitAllocationTest2.ChannelTestBean channelTestBean = inputSamplingList.get(i);
                    list.add(channelTestBean);
                }
            }
            groupList.add(list);
        }
        return groupList;
    }




    public static List<CircuitAllocationTest2.ChannelTestBean> getInputChannel(){
        List<CircuitAllocationTest2.ChannelTestBean> samplingList = new ArrayList<>();
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(1,36));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(2,42));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(3,33));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(4,34));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(5,41));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(6,50));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(7,42));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(8,36));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(9,42));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(10,33));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(11,34));
        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(12,41));
        //samplingList.add(new CircuitAllocationTest2.ChannelTestBean(13,50));
//        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(14,42));
//        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(15,36));
//        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(16,42));
//        samplingList.add(new CircuitAllocationTest2.ChannelTestBean(17,33));

        return samplingList;
    }


    public static List<CircuitAllocationTest2.CircuitTestBean> getCircuitTestList(){
        List<CircuitAllocationTest2.CircuitTestBean> circuitTestList = new ArrayList<>();
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(1,42,8));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(2,50,8));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(3,42,4));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(4,41,4));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(5,51,2));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(6,50,2));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(7,30,1));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(8,50,1));
        circuitTestList.add(new CircuitAllocationTest2.CircuitTestBean(9,50,16));

        return circuitTestList;
    }


    public static class ChannelTestBean implements  Comparable<CircuitAllocationTest2.ChannelTestBean>, Serializable {
        private Integer sampling;
        private Integer index = 0;

        public ChannelTestBean() {

        }
        public ChannelTestBean(Integer index,Integer sampling) {
            this.index = index;
            this.sampling = sampling;
        }

        public Integer getSampling() {
            return sampling;
        }

        public void setSampling(Integer sampling) {
            this.sampling = sampling;
        }

        public Integer getIndex() {
            return index;
        }

        public void setIndex(Integer index) {
            this.index = index;
        }

        @Override
        public int compareTo(CircuitAllocationTest2.ChannelTestBean o) {
            return this.getSampling()-o.getSampling();
        }
    }

    public static class CircuitGroupTestBean extends HashMap<Integer, CircuitAllocationTest2.CircuitTestBean> implements Serializable {

        public CircuitGroupTestBean( ){
        }
        public CircuitGroupTestBean( CircuitGroupTestBean t){
            super(t);
        }

        public void putAll(HashMap <Integer, CircuitAllocationTest2.CircuitTestBean> m) {
            Set<Integer> set = m.keySet();
            for(Integer key : set){
                if(this.containsKey(key)){
                    CircuitAllocationTest2.CircuitTestBean c1 = this.get(key).clone();
                    CircuitAllocationTest2.CircuitTestBean c2 =  m.get(key).clone();
                    for(CircuitAllocationTest2.ChannelTestBean ct : c2.getChannelTestList()){
                        if(!c1.getChannelTestList().contains(ct)){
                            c1.getChannelTestList().add(ct);
                        }
                    }
                    c1.setCount((int)Math.ceil(1.0 * c1.getChannelTestList().size()/c1.getChannelNum()));
                    this.put(key,c1);
                }else{
                    this.put(key,m.get(key).clone());

                }
            }
        }

        public CircuitAllocationTest2.CircuitTestBean put(Integer key, CircuitAllocationTest2.CircuitTestBean ct) {
            return super.put(key,ct.clone());
        }

        public Integer getAllChannelCount(){
            Set<Integer> set = this.keySet();
            Integer count = 0;
            for(Integer key : set){
                CircuitAllocationTest2.CircuitTestBean c1 = this.get(key);
                count += c1.getCount()*c1.getChannelNum();
            }
            return count;
        }

        public Integer getCircuitCount(){
            Set<Integer> set = this.keySet();
            Integer count = 0;
            for(Integer key : set){
                CircuitAllocationTest2.CircuitTestBean c1 = this.get(key);
                count += c1.getCount();
            }
            return count;
        }

        @Override
        public CircuitAllocationTest2.CircuitGroupTestBean clone() {
            return (CircuitAllocationTest2.CircuitGroupTestBean)SerializationUtils.clone(this);
        }




    }



    public static class CircuitTestBean implements Cloneable,Serializable{

        private Integer id;
        private Integer sampling;
        private Integer channelNum;
        private Integer count;

        private List<CircuitAllocationTest2.ChannelTestBean> channelTestList;


        public CircuitTestBean() {

        }

        public CircuitTestBean(Integer id, Integer sampling,Integer channelNum) {
            this.id = id;
            this.sampling = sampling;
            this.channelNum = channelNum;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Integer getSampling() {
            return sampling;
        }

        public void setSampling(Integer sampling) {
            this.sampling = sampling;
        }

        public Integer getChannelNum() {
            return channelNum;
        }

        public void setChannelNum(Integer channelNum) {
            this.channelNum = channelNum;
        }

        public Integer getCount() {
            return count;
        }

        public void setCount(Integer count) {
            this.count = count;
        }
        public List<CircuitAllocationTest2.ChannelTestBean> getChannelTestList() {
            return channelTestList;
        }

        public void setChannelTestList(List<CircuitAllocationTest2.ChannelTestBean> channelTestList) {
            this.channelTestList = channelTestList;
        }

        @Override
        public CircuitAllocationTest2.CircuitTestBean clone() {
            return (CircuitAllocationTest2.CircuitTestBean)  SerializationUtils.clone(this);
        }

    }
}
